#include "Util.hpp"
#include "Services.hpp"


class TcpServer;

class ThreadData
{
public:
    int sock_;
    uint16_t cltPort_;
    std::string cltIp_;
    TcpServer* ts_;

    ThreadData(int sock, uint16_t cltPort, const std::string& cltIp, TcpServer* ts)
        : sock_(sock)
        , cltPort_(cltPort)
        , cltIp_(cltIp)
        , ts_(ts)
    {}
};

extern int g_logfd;

// 执行指令
void exec_command(int sock, const std::string& ip, uint16_t port)
{
    assert(sock >= 0 && !ip.empty() && port >= 1024);
    char command[1024];
    while (true)
    {
        ssize_t rd = read(sock, command, sizeof(command) - 1); // 读取数据
        if (rd > 0) // 读取到了数据
        {
            command[rd] = '\0';
            std::cout << "DEBUG: 读取到的数据为：" << command << std::endl;
            // 执行完 popen 会将数据写入到 FILE* fp 对应的文件中, 但是还没有刷新
            // 所以数据暂时在 语言层面的 FILE 结构体中的缓冲区中 
            // care of safe
            std::string confirm_safe = command; 
            if ((std::string::npos != confirm_safe.find("rm")) || (std::string::npos != confirm_safe.find("unlink")))
            {
                break;
            }

            FILE* fp = popen(command, "r");
            if (fp == nullptr)
                break;
            std::cout << "success: popen" << std::endl;
            // 执行完成之后, 结果会被写入到这个管道中
            char content[1024];
            // 读取出来, 从这个管道文件流中读取出来
            while (fgets(content, sizeof(content) - 1, fp) != nullptr)
            {
                // 写回 sock 内核文件中
                write(sock, content, strlen(content));
            }

            // dup2(fp, 1)
            // 本来应该向这个 fp 文件中输入数据, 现在重定向为向网络里打印数据
            // dup2(sock, fp->_fileno);
            // 刷新这个文件描述符的数据, 这里的刷新, 实际上是对 fp->_fileno 下标对应的进行刷新
            // 但是下标对应的文件描述符被修改了
            // fflush(fp); 
            pclose(fp); // 再关闭这个文件

        }
        else 
        {
            break;
        }
    }
}

class TcpServer
{
public:
    TcpServer(uint16_t port, const std::string& ip = nullptr)
        : listenSock_(-1) // 表示还没连接
        , port_(port)     // 端口号
        , ip_(ip)         // IP 地址，一般让 os 自动绑定
        , thdpool_(nullptr) // 线程池
        , quit_(false)
    {}

    void init()
    {
        // 1. 创建 socket
        //    AF_INET 表示需要网络连接, SOCK_STREAM, 表示字节流, 0 表示默认按照 TCP 来
        listenSock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)  // 创建 socket 失败
        {
            logMsg(FATAL, "socket: %s", strerror(errno));
            exit(SOCK_ERR);
        }
        logMsg(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);
        // 2. bind 
        // 2.1 填充服务器信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof local); // 初始化置空
        local.sin_family = AF_INET; // 表示需要网络通信
        local.sin_port = htons(port_); // 端口号 -> 主机转网络
        ip_.empty() ? (local.sin_addr .s_addr = INADDR_ANY) : inet_aton(ip_.c_str(), &local.sin_addr);  // ip 地址转化成32位整数, 并且转化成网络数据
        // 2.2 本地 socket 写入 socket 对应的内核区域
        if (bind(listenSock_, (struct sockaddr*) &local, sizeof local) != 0) 
        { // bind 失败
            logMsg(FATAL, "bind: %s, %d", strerror(errno));
            exit(BIND_ERR);
        }
        logMsg(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);
        // 3. 监听 socket
        if (listen(listenSock_, 5) < 0)
        {
            logMsg(FATAL, "listen: %s, %d", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMsg(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);

        // 启动线程池
        thdpool_ = ThreadPool<Task>::getInstance();
    }


    void loop()
    {
        // 接收子进程的 SIGCHLD 信号, 手动设置成忽略, 让其自动释放空间
        // signal(SIGCHLD, SIG_IGN);
        thdpool_->start();

        while (!quit_)
        {
            // 4. 获取连接, accept 会得到一个新的 socketfd
            struct sockaddr_in clt; // 客户端
            socklen_t len = sizeof clt; // 这个结构体的大小
            // 最后两个参数是输入型也是输出型参数, 收到连接之后会得到 发送方的一些基本数据
            // listenSock_, 也就是服务器自身的 sockfd 就是一个接收人的身份, 返回值才是真正的服务员
            int serverSock = accept(listenSock_, (struct sockaddr*) &clt, &len);
            if (serverSock < 0) 
            {
                logMsg(WARNING, "accpet: %s[%d]", strerror(errno), serverSock);
                continue ; // 连接失败重新进入循环
            }
            // 4.1 获取客户端端（连接方）的基本信息
            // IP 地址在网络传输的过程中就是一个 uint32_t, inet_ntoa 将这个整数重新转化成点分十进制
            std::string cltIp = inet_ntoa(clt.sin_addr);
            uint16_t cltPort = ntohs(clt.sin_port); // 接收 客户端(发送端) 的端口号
            logMsg(DEBUG, "accept: %s | %s[%d] sock fd: %d", strerror(errno), cltIp.c_str(), cltPort, serverSock);
            // 5. 提供服务 echo -> 小写转大写
            // handle_server(serverSock, cltIp, cltPort); // 这是个单线程版本, 只可以为一个线程提供服务

            // // 5.1 多进程版本
            // pid_t id = fork();
            // assert(id != -1);
            // if (id == 0) 
            // { // 子进程提供服务
            //     // 子进程会继承父进程的文件描述符表
            //     close(listenSock_); // 关闭
            //     handle_server(serverSock, cltIp, cltPort);
            //     exit(0); // 进入僵尸状态
            // }
            // // 父进程, 回收文件描述符
            // close(serverSock);

            // // 5.1.1 多进程升级版
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     close(listenSock_);
            //     // 直接让子进程结束
            //     if (fork() > 0) exit(0);
            //     // 然后孙子进程就没有父进程了, 就会被系统领养, 由系统来进行回收
            //     handle_server(serverSock, cltIp, cltPort);
            //     exit(0);
            // }
            // // master 进程
            // close(serverSock);
            // // 父进程被直接终止了, 当前进程就会立即获取退出码, 进行可以马上将这个僵尸进程回收
            // pid_t ret = waitpid(id, nullptr, 0); // 阻塞式等待
            // assert(ret > 0);

            // // 5.2 多线程
            // // 这里不需要关闭文件描述符, 因为多线程会共享文件描述符表
            // // 如果关闭可能会影响到 master
            // pthread_t thd ;
            // ThreadData* thdData = new ThreadData(serverSock, cltPort, cltIp, this);
            // pthread_create(&thd, nullptr, threadRoutine, (void*) thdData);

            // 5.3 线程池
            // task tsk(serversock, cltip, cltport, std::bind(&tcpserver::handle_server, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); // 构建任务
            // thdpool_->push(tsk); // 将任务放到队列中, 其中的线程会及时发现任务
            // std::cout << "new user coming , current thread num = " << thdpool_->threadcount() << std::endl;

            // 5.4   创建一个任务, 然后指定服务的类型, 也就是 exec_command 或者 handle_server 等服务
            // Task tsk(serverSock, cltIp, cltPort, exec_command);
            // thdpool_->push(tsk);

            // 5.5 实现网络计算器
            Task tsk(serverSock, cltIp, cltPort, net_calculate);
            thdpool_->push(tsk);
        }
    }



    static void* threadRoutine(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* data = static_cast<ThreadData*>(args);
        data->ts_->handle_server(data->sock_, data->cltIp_, data->cltPort_);
        return nullptr;
    }


    // 进行对收到的数据进行处理
    // tcp && udp 都支持全双工, 支持同时读和写
    void handle_server(int sock, const std::string& cliIp, uint16_t cliPort)
    {
        assert(sock >= 0 && !cliIp.empty() && cliPort >= 1024);
        char recvmsg[1024];
        while (true)
        {
            // sock 就是客户端和服务端进行网络通信的 内核文件
            // 既然是文件, tcp 中就需要使用 read/write 进行读取/接收数据
            ssize_t rd = read(sock, recvmsg, sizeof(recvmsg) - 1); // 读取客户端发送的数据
            if (rd > 0) 
            {   // 如果用户输入的数据是 quit 那么结束
                recvmsg[rd] = '\0';
                std::cout << "Debug: server recvfrom client -> " << recvmsg << std::endl;
                if (strcasecmp(recvmsg, "quit") == 0) // 忽略大小进行比较
                {
                    logMsg(DEBUG, "client quit  %s[%d]", cliIp.c_str(), cliPort);
                    break;
                }
                for (int i = 0; i < rd; i ++)
                {
                    if (isalpha(recvmsg[i]) && islower(recvmsg[i])) recvmsg[i] = toupper(recvmsg[i]);
                }

                // 将处理完成的数据再写回 这个内核文件中
                write(sock, recvmsg, strlen(recvmsg));
            }
            else if (rd == 0) 
            { // 说明 连接结束, 因为写端不写了，也就是客户端不写了, rdret 就会是 0
                logMsg(DEBUG, "client close  %s[%d]", cliIp.c_str(), cliPort);
                break;
            }
            else if (rd < 0)
            { // 说明连接错误
                logMsg(DEBUG, "%s[%d] -> read: %s", cliIp.c_str(), cliPort, strerror(errno));
                break;
            }
        }
        // 服务结束, 结束本次连接
        close(sock);
        logMsg(DEBUG, "finish current connection -> %d", sock);
        // 如果一个进行对应的文件fd, 打开了但是没有释放, 就是文件描述符的泄露
        // 因为文件描述符表是一个结构, 一般上限是 10w 或者 65535, 用完了就无法进行连接
    }

    void quitServer()
    {
        quit_ = true;
    }

    ~TcpServer()
    {
        if (listenSock_ >= 0)
        {

        }
    }
private:
    int listenSock_; // 监听套接字
    uint16_t port_;   // 端口号
    std::string ip_;  // 服务端 ip 地址
    ThreadPool<Task>* thdpool_; // 线程池
    bool quit_;
};

static void Usage()
{
    std::cerr << "正确打开方式 -> ./tcpServer server_port(服务器要绑定的端口) server_ip(服务器要绑定的ip, 推荐不填)" << std::endl;
    std::cerr << "比如： ./tcpServer 44444 127.0.0.1" << std::endl;
}

TcpServer* p_svr = nullptr;

void handler(int signo)
{
    if (signo == 3 && p_svr != nullptr)
        p_svr -> quitServer();
}

// Usage: ./tcpServer server_port server_ip
int main(int argc, char* argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage();
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3) ip = argv[2];

    signal(3, handler);
    // 启动日志
    // Log logger;
    // logger.launch();

    TcpServer svr(port, ip);
    svr.init();
    p_svr = &svr;
    svr.loop();
    return 0;
}